

#include "iobmp.h"

#include <fstream>
#include <vector>
#include <cassert>
#include <memory>
#include <cstring>
#include "texels.h"

#define BYTES_INT_32 4
#define ALINEACION 4
#define BYTES_SHORT_16 2

#define BYTES_MAGICO 2
#define FIRMA_MAGICA "BM"
#define BYTES_CABECERA_SIN_USAR 8

#define BYTES_DIB 40
#define strBYTES_DIB "40"

#define BPP 24
#define strBPP "24"

#define COMPRESION 0

#define BYTES_RGB 3


void IObmp::leerInt(int &i) {
   i = 0;
   archivo.read(buffer, BYTES_INT_32);
   memcpy(&i, buffer, BYTES_INT_32);
}

void IObmp::leerShort(int &i) {
   i = 0;
   archivo.read(buffer, BYTES_SHORT_16);
   memcpy(&i, buffer, BYTES_SHORT_16);
}


void IObmp::procesarCabeceraBMP() {
   char firmaMagica[BYTES_MAGICO];
   char sinUsar[BYTES_CABECERA_SIN_USAR];

   archivo.read(firmaMagica, BYTES_MAGICO);

   if(strncmp(firmaMagica, FIRMA_MAGICA, BYTES_MAGICO) != 0) {
      throw IObmp::Excepcion("No es un archivo BMP");
   }

   archivo.read(sinUsar, BYTES_CABECERA_SIN_USAR);
   leerInt(posicionMapa);
}

void IObmp::procesarCabeceraDIB() {
   int longitudCabecera;

   int planos;

   int profundidad;
   int compresion;

   leerInt(longitudCabecera);

   if(longitudCabecera != BYTES_DIB) {
      throw IObmp::FormatoNoImplementado("Solo esta soportado el formato DIB" 
                                          "cuya cabecera es de "strBYTES_DIB" bytes.");
   }

   leerInt(ancho);
   leerInt(alto);

   leerShort(planos);

   leerShort(profundidad);
   leerInt(compresion);

   if(profundidad != BPP) {
      throw IObmp::FormatoNoImplementado("Solo esta soportado el formato RGB de "strBPP" bits.");
   }
   if(compresion != COMPRESION) {
      throw IObmp::FormatoNoImplementado("Solo esta soportado el formato sin comprimir.");
   }
}

std::vector<unsigned char>& IObmp::procesarMapaRGB() {
   archivo.seekg(posicionMapa, std::ios_base::beg);
   archivo.exceptions(std::ifstream::failbit | std::ifstream::badbit);

   std::vector<unsigned char> &mapa = *new std::vector<unsigned char>(ancho * alto * BYTES_RGB);
   std::auto_ptr<std::vector<unsigned char> > guardia(&mapa);

   int padding = (ALINEACION - ((ancho * BYTES_RGB) % ALINEACION)) % ALINEACION;
   int longitudBuffer = (ancho * BYTES_RGB) + padding;

   std::vector<char> bufferFila(longitudBuffer);

   for(int fila = alto - 1; fila >= 0 ; --fila) {
      if(archivo.eof()) {
         throw IObmp::Excepcion("Se alcanzo el fin de archivo antes de lo previsto.");
      }

      archivo.read(&bufferFila[0], longitudBuffer);

      for(int col = 0; col < ancho; ++col){
         int posicionBase = ((fila * ancho) + col) * BYTES_RGB;
         int indiceBuffBase = col * BYTES_RGB;

         for(int indice = 0; indice < BYTES_RGB; ++indice){
            mapa[posicionBase + indice] = 
               (unsigned char)bufferFila[indiceBuffBase + (BYTES_RGB - indice - 1)];
         }
      }
   }

   return *guardia.release();
}

std::vector<unsigned char>& IObmp::procesarBMP() {
   procesarCabeceraBMP();
   procesarCabeceraDIB();
   return procesarMapaRGB();
}


/*
 * Procesa el archivo, obteniendo un vector con el mapa de bits del BMP.
 *
 * Se encarga de cerrar ordenadamente el archivo tanto en el caso de una
 * finalizacion correcta como en el caso de que surja una excepcion.
 * */
std::vector<unsigned char>& IObmp::procesarArchivoRGB(const std::string &nombre) {
   assert(not archivo.is_open());

   archivo.clear();
   archivo.open(nombre.c_str());
   if(not archivo.is_open()) throw IObmp::Excepcion("No se pudo abrir el archivo.");
   archivo.exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);

   std::vector<unsigned char> *mapa = 0;
   try {
      mapa = &procesarBMP();
   }
   catch(...){
      archivo.close();
      throw;
   }
   archivo.close();
   return *mapa;
}


IObmp::IObmp() {}

Texels& IObmp::cargarBMP(const std::string &nombre) {
   std::vector<unsigned char> &mapa = procesarArchivoRGB(nombre);
   std::auto_ptr<std::vector<unsigned char> > guardia(&mapa);

   Texels &texels = *new Texels(ancho, alto, mapa);
   guardia.release();

   return texels;
}

